package com.baidu.ueditor;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.ResourceUtils;

import com.baidu.ueditor.define.ActionMap;
import com.kevin.model.config.PropertiesModel;

import lombok.extern.slf4j.Slf4j;

/**
 * 配置管理器
 * 
 * @author hancong03@baidu.com
 *
 */
@Slf4j
public final class ConfigManager {

    private final String rootPath;
    private final String contextPath;
    private static final String configFileName = "/ueditor_config.json";
    private JSONObject jsonConfig = null;
    // 涂鸦上传filename定义
    private final static String SCRAWL_FILE_NAME = "scrawl";
    // 远程图片抓取filename定义
    private final static String REMOTE_FILE_NAME = "remote";

    /*
     * 通过一个给定的路径构建一个配置管理器， 该管理器要求地址路径所在目录下必须存在config.properties文件
     */
    private ConfigManager(String rootPath, String contextPath, String uri) throws FileNotFoundException, IOException {

        if (rootPath == null) {
            rootPath = "";
        }
        rootPath = rootPath.replace("\\", "/");

        this.rootPath = PropertiesModel.CONFIG.getString("uploadFilePath", rootPath); // 文件上传物理路径
        this.contextPath = contextPath;

        this.initEnv();

    }

    /**
     * 配置管理器构造工厂
     * 
     * @param rootPath
     *            服务器根路径
     * @param contextPath
     *            服务器所在项目路径
     * @param uri
     *            当前访问的uri
     * @return 配置管理器实例或者null
     */
    public static ConfigManager getInstance(String rootPath, String contextPath, String uri) {

        try {
            return new ConfigManager(rootPath, contextPath, uri);
        } catch (Exception e) {
            log.error("获取配置管理器出错！", e);
            return null;
        }

    }

    // 验证配置文件加载是否正确
    public boolean valid() {
        return this.jsonConfig != null;
    }

    public JSONObject getAllConfig() {

        return this.jsonConfig;

    }

    public Map<String, Object> getConfig(int type) {

        Map<String, Object> conf = new HashMap<String, Object>();
        String savePath = null;

        switch (type) {

            case ActionMap.UPLOAD_FILE:
                conf.put("isBase64", "false");
                conf.put("maxSize", this.jsonConfig.getLong("fileMaxSize"));
                conf.put("allowFiles", this.getArray("fileAllowFiles"));
                conf.put("fieldName", this.jsonConfig.getString("fileFieldName"));
                savePath = this.jsonConfig.getString("filePathFormat");
                break;

            case ActionMap.UPLOAD_IMAGE:
                conf.put("isBase64", "false");
                conf.put("maxSize", this.jsonConfig.getLong("imageMaxSize"));
                conf.put("allowFiles", this.getArray("imageAllowFiles"));
                conf.put("fieldName", this.jsonConfig.getString("imageFieldName"));
                savePath = this.jsonConfig.getString("imagePathFormat");

                break;

            case ActionMap.UPLOAD_VIDEO:
                conf.put("maxSize", this.jsonConfig.getLong("videoMaxSize"));
                conf.put("allowFiles", this.getArray("videoAllowFiles"));
                conf.put("fieldName", this.jsonConfig.getString("videoFieldName"));
                savePath = this.jsonConfig.getString("videoPathFormat");
                break;

            case ActionMap.UPLOAD_SCRAWL:
                conf.put("filename", ConfigManager.SCRAWL_FILE_NAME);
                conf.put("maxSize", this.jsonConfig.getLong("scrawlMaxSize"));
                conf.put("fieldName", this.jsonConfig.getString("scrawlFieldName"));
                conf.put("isBase64", "true");
                savePath = this.jsonConfig.getString("scrawlPathFormat");
                break;

            case ActionMap.CATCH_IMAGE:
                conf.put("filename", ConfigManager.REMOTE_FILE_NAME);
                conf.put("filter", this.getArray("catcherLocalDomain"));
                conf.put("maxSize", this.jsonConfig.getLong("catcherMaxSize"));
                conf.put("allowFiles", this.getArray("catcherAllowFiles"));
                conf.put("fieldName", this.jsonConfig.getString("catcherFieldName") + "[]");
                savePath = this.jsonConfig.getString("catcherPathFormat");
                break;

            case ActionMap.LIST_IMAGE:
                conf.put("allowFiles", this.getArray("imageManagerAllowFiles"));
                conf.put("dir", this.jsonConfig.getString("imageManagerListPath"));
                conf.put("count", this.jsonConfig.getInt("imageManagerListSize"));
                break;

            case ActionMap.LIST_FILE:
                conf.put("allowFiles", this.getArray("fileManagerAllowFiles"));
                conf.put("dir", this.jsonConfig.getString("fileManagerListPath"));
                conf.put("count", this.jsonConfig.getInt("fileManagerListSize"));
                break;

        }

        conf.put("savePath", savePath);
        conf.put("rootPath", this.rootPath);

        return conf;

    }

    private void initEnv() throws FileNotFoundException, IOException {

        String configContent = this.readFile(ConfigManager.configFileName);
        log.warn(this.getConfigPath() + "");
        log.warn("content====" + configContent);
        try {
            String domain = PropertiesModel.CONFIG.getString("file.domainPath", "");

            // 自定义处理项目名 begin
            configContent = configContent.replaceAll("\\$\\{\\s+", "\\$\\{").replaceAll("\\s+\\}", "\\}")
                    .replaceAll("\\$\\{applicationScopetion.server.context-path\\}", contextPath)
                    .replaceAll("\\$\\{config.fileDomainPath\\}", domain);
            // configContent = configContent.replaceAll("/+", "/");
            // 自定义处理项目名 end`
            JSONObject jsonConfig = new JSONObject(configContent);
            this.jsonConfig = jsonConfig;
        } catch (Exception e) {
            e.printStackTrace();
            this.jsonConfig = null;
        }

    }

    private URL getConfigPath() {
        // jar包内路径 jar:file:/F:/workspaceec4/fileout/target/fileout.war!/WEB-INF/classes!/ueditor_config.json
        URL url = this.getClass().getResource(ConfigManager.configFileName);
        return url;
    }

    private String[] getArray(String key) {

        JSONArray jsonArray = this.jsonConfig.getJSONArray(key);
        String[] result = new String[jsonArray.length()];

        for (int i = 0, len = jsonArray.length(); i < len; i++) {
            result[i] = jsonArray.getString(i);
        }

        return result;

    }

    private String readFile(String path) throws IOException {
//        ApplicationHome home = new ApplicationHome(getClass());
//        File jarFile = home.getSource();
//        return jarFile.getParentFile();

        // 获取跟目录
        String tmpContent = "{}";
        Set<String> configFiles = new LinkedHashSet<>();
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:" + path);
        if (resources != null && resources.length > 0) {
            for (Resource resource : resources) {
                log.error("========Load " + resource);
                configFiles.add("classpath:" + resource.getFilename());
            }
        }
        configFiles.add("file:." + path); // 当前目录，用于打成jar包运行，配置文件放到jar包所在的同级目录
        log.error("========configFiles size " + configFiles.size());
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        for (String location : configFiles) {
            try {
                Resource resource = resourceLoader.getResource(location);
                if (resource.exists()) {
                    tmpContent = readInputStream(resource.getInputStream());
                    log.error("Load " + location + " success!!! ");
                } else {
                    log.error("Load " + location + " not exists. ");
                }
            } catch (Exception e) {
                log.error("Load " + location + " failure. ", e);
            }
        }

        File dir = new File(ResourceUtils.getURL("classpath:").getPath());
        if (!dir.exists())
            dir = new File("");
        log.warn("path:" + dir.getAbsolutePath());

        // 如果上传目录为/static/images/upload/，则可以如下获取：
        File upload = new File(dir.getAbsolutePath(), "static/images/upload/");
        if (!upload.exists())
            upload.mkdirs();
        log.warn("upload url:" + upload.getAbsolutePath());
        // 在开发测试模式时，得到的地址为：{项目根目录}/target/classes/static/images/upload/
        // 在打包成jar正式发布时，得到的地址为：{发布jar包目录}/static/images/upload/1

//        tmpContent = FileUtils.readFileToString(new File(dir, path));
        try {
            getProperties(null, "ueditor_config.json");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return this.filter(tmpContent);

    }

    private String readInputStream(InputStream in) throws IOException {

        StringBuilder builder = new StringBuilder();

        try (InputStreamReader reader = new InputStreamReader(in, "UTF-8")) {

            BufferedReader bfReader = new BufferedReader(reader);

            String tmpContent = null;

            while ((tmpContent = bfReader.readLine()) != null) {
                builder.append(tmpContent);
            }

            bfReader.close();

        } catch (Exception e) {
            // 忽略
            log.error("", e);
        }

        return this.filter(builder.toString());

    }

    // 过滤输入字符串, 剔除多行注释以及替换掉反斜杠
    private String filter(String input) {

        return input.replaceAll("/\\*[\\s\\S]*?\\*/", "");

    }

    private void getProperties(String systemPropertyName, String fileName) throws Exception {
        String requestedFile = null;
        if (StringUtils.isNotBlank(systemPropertyName)) {
            requestedFile = System.getProperty(systemPropertyName);
        }
        if (StringUtils.isBlank(fileName)) {
            return;
        }
        String propFileName = requestedFile != null ? requestedFile : fileName;
        File propFile = new File(propFileName);
        // 普通运行ueditor_config.json > F:/workspaceec4/fileout/ueditor_config.json
        // /ueditor_config.json > F:/ueditor_config.json
        // jar包运行ueditor_config.json > F:/workspaceec4/fileout/target/ueditor_config.json
        // /ueditor_config.json > F:/ueditor_config.json

        Properties props = new Properties();

        InputStream in = null;

        String propSrc = null;

        try {
            log.warn(propFile.getAbsolutePath());
            if (propFile.exists()) { // 文件存在
                try {
                    if (requestedFile != null) {
                        propSrc = "specified file: '" + requestedFile + "'";
                    } else {
                        propSrc = "default file in current working dir: '" + fileName + "'";
                    }

                    in = new BufferedInputStream(new FileInputStream(propFileName));
                    props.load(in);

                } catch (IOException ioe) {
                    throw new Exception("Properties file: '" + propFileName + "' could not be read.", ioe);
                }
            } else if (requestedFile != null) {
                in = Thread.currentThread().getContextClassLoader().getResourceAsStream(requestedFile);

                if (in == null) {
                    throw new Exception("Properties file: '" + requestedFile + "' could not be found.");
                }

                propSrc = "specified file: '" + requestedFile + "' in the class resource path.";

                in = new BufferedInputStream(in);
                try {
                    props.load(in);
                } catch (IOException ioe) {
                    throw new Exception("Properties file: '" + requestedFile + "' could not be read.", ioe);
                }

            } else {
                propSrc = "default resource file in Quartz package: '" + fileName + "'";

                ClassLoader cl = getClass().getClassLoader();
                if (cl == null)
                    cl = findClassloader();
                if (cl == null)
                    throw new Exception("Unable to find a class loader on the current thread or class.");

                in = cl.getResourceAsStream(fileName); // 类路径的根目录

                if (in == null) {
                    in = cl.getResourceAsStream("com/kevin/" + fileName);
                }
                if (in == null) {
                    throw new Exception("Default " + fileName + " not found in class path");
                }
                try {
                    props.load(in);
                } catch (IOException ioe) {
                    throw new Exception("Resource properties file: 'com/kevin/" + fileName + "' " + "could not be read from the classpath.",
                            ioe);
                }
            }
        } finally {
            log.warn(propSrc);
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ignore) {
                    /* ignore */ }
            }
        }

    }

    private ClassLoader findClassloader() {
        // work-around set context loader for windows-service started jvms (QUARTZ-748)
        if (Thread.currentThread().getContextClassLoader() == null && getClass().getClassLoader() != null) {
            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
        }
        return Thread.currentThread().getContextClassLoader();
    }

}
